/*
* Copyright 2003-2010 Tufts University  Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 * 
 * http://www.osedu.org/licenses/ECL-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package ls.vuecp.gui.shape;

import java.awt.geom.Rectangle2D;
import java.awt.geom.PathIterator;
import java.util.Vector;

final class Order1 extends Curve {
    private double x0;
    private double y0;
    private double x1;
    private double y1;
    private double xmin;
    private double xmax;

    public Order1(double x0, double y0,
		  double x1, double y1,
		  int direction)
    {
	super(direction);
	this.x0 = x0;
	this.y0 = y0;
	this.x1 = x1;
	this.y1 = y1;
	if (x0 < x1) {
	    this.xmin = x0;
	    this.xmax = x1;
	} else {
	    this.xmin = x1;
	    this.xmax = x0;
	}
    }

    public int getOrder() {
	return 1;
    }

    public double getXTop() {
	return x0;
    }

    public double getYTop() {
	return y0;
    }

    public double getXBot() {
	return x1;
    }

    public double getYBot() {
	return y1;
    }

    public double getXMin() {
	return xmin;
    }

    public double getXMax() {
	return xmax;
    }

    public double getX0() {
	return (direction == INCREASING) ? x0 : x1;
    }

    public double getY0() {
	return (direction == INCREASING) ? y0 : y1;
    }

    public double getX1() {
	return (direction == DECREASING) ? x0 : x1;
    }

    public double getY1() {
	return (direction == DECREASING) ? y0 : y1;
    }

    public double XforY(double y) {
	if (x0 == x1 || y == y0) {
	    return x0;
	}
	if (y == y1) {
	    return x1;
	}
	// assert(y0 != y1); /* No horizontal lines... */
	return (x0 + (y - y0) * (x1 - x0) / (y1 - y0));
    }

    public double TforY(double y) {
	return (y - y0) / (y1 - y0);
    }

    public double XforT(double t) {
	return x0 + t * (x1 - x0);
    }

    public double YforT(double t) {
	return y0 + t * (y1 - y0);
    }

    public double dXforT(double t, int deriv) {
	switch (deriv) {
	case 0:
	    return x0 + t * (x1 - x0);
	case 1:
	    return (x1 - x0);
	default:
	    return 0;
	}
    }

    public double dYforT(double t, int deriv) {
	switch (deriv) {
	case 0:
	    return y0 + t * (y1 - y0);
	case 1:
	    return (y1 - y0);
	default:
	    return 0;
	}
    }

    public double nextVertical(double t0, double t1) {
	return t1;
    }

    public boolean accumulateCrossings(Crossings c) {
	double xlo = c.getXLo();
	double ylo = c.getYLo();
	double xhi = c.getXHi();
	double yhi = c.getYHi();
	if (xmin >= xhi) {
	    return false;
	}
	double xstart, ystart, xend, yend;
	if (y0 < ylo) {
	    if (y1 <= ylo) {
		return false;
	    }
	    ystart = ylo;
	    xstart = XforY(ylo);
	} else {
	    if (y0 >= yhi) {
		return false;
	    }
	    ystart = y0;
	    xstart = x0;
	}
	if (y1 > yhi) {
	    yend = yhi;
	    xend = XforY(yhi);
	} else {
	    yend = y1;
	    xend = x1;
	}
	if (xstart >= xhi && xend >= xhi) {
	    return false;
	}
	if (xstart > xlo || xend > xlo) {
	    return true;
	}
	c.record(ystart, yend, direction);
	return false;
    }

    public void enlarge(Rectangle2D r) {
	r.add(x0, y0);
	r.add(x1, y1);
    }

    public Curve getSubCurve(double ystart, double yend, int dir) {
	if (ystart == y0 && yend == y1) {
	    return getWithDirection(dir);
	}
	if (x0 == x1) {
	    return new Order1(x0, ystart, x1, yend, dir);
	}
	double num = x0 - x1;
	double denom = y0 - y1;
	double xstart = (x0 + (ystart - y0) * num / denom);
	double xend = (x0 + (yend - y0) * num / denom);
	return new Order1(xstart, ystart, xend, yend, dir);
    }

    public Curve getReversedCurve() {
	return new Order1(x0, y0, x1, y1, -direction);
    }

    public int compareTo(Curve other, double yrange[]) {
	if (!(other instanceof Order1)) {
	    return super.compareTo(other, yrange);
	}
	Order1 c1 = (Order1) other;
	if (yrange[1] <= yrange[0]) {
	    throw new InternalError("yrange already screwed up...");
	}
	yrange[1] = Math.min(Math.min(yrange[1], y1), c1.y1);
	if (yrange[1] <= yrange[0]) {
	    throw new InternalError("backstepping from "+yrange[0]+" to "+yrange[1]);
	}
	if (xmax <= c1.xmin) {
	    return (xmin == c1.xmax) ? 0 : -1;
	}
	if (xmin >= c1.xmax) {
	    return 1;
	}
	/*
	 * If "this" is curve A and "other" is curve B, then...
	 * xA(y) = x0A + (y - y0A) (x1A - x0A) / (y1A - y0A)
	 * xB(y) = x0B + (y - y0B) (x1B - x0B) / (y1B - y0B)
	 * xA(y) == xB(y)
	 * x0A + (y - y0A) (x1A - x0A) / (y1A - y0A)
	 *    == x0B + (y - y0B) (x1B - x0B) / (y1B - y0B)
	 * 0 == x0A (y1A - y0A) (y1B - y0B) + (y - y0A) (x1A - x0A) (y1B - y0B)
	 *    - x0B (y1A - y0A) (y1B - y0B) - (y - y0B) (x1B - x0B) (y1A - y0A)
	 * 0 == (x0A - x0B) (y1A - y0A) (y1B - y0B)
	 *    + (y - y0A) (x1A - x0A) (y1B - y0B)
	 *    - (y - y0B) (x1B - x0B) (y1A - y0A)
	 * If (dxA == x1A - x0A), etc...
	 * 0 == (x0A - x0B) * dyA * dyB
	 *    + (y - y0A) * dxA * dyB
	 *    - (y - y0B) * dxB * dyA
	 * 0 == (x0A - x0B) * dyA * dyB
	 *    + y * dxA * dyB - y0A * dxA * dyB
	 *    - y * dxB * dyA + y0B * dxB * dyA
	 * 0 == (x0A - x0B) * dyA * dyB
	 *    + y * dxA * dyB - y * dxB * dyA
	 *    - y0A * dxA * dyB + y0B * dxB * dyA
	 * 0 == (x0A - x0B) * dyA * dyB
	 *    + y * (dxA * dyB - dxB * dyA)
	 *    - y0A * dxA * dyB + y0B * dxB * dyA
	 * y == ((x0A - x0B) * dyA * dyB
	 *       - y0A * dxA * dyB + y0B * dxB * dyA)
	 *    / (-(dxA * dyB - dxB * dyA))
	 * y == ((x0A - x0B) * dyA * dyB
	 *       - y0A * dxA * dyB + y0B * dxB * dyA)
	 *    / (dxB * dyA - dxA * dyB)
	 */
	double dxa = x1 - x0;
	double dya = y1 - y0;
	double dxb = c1.x1 - c1.x0;
	double dyb = c1.y1 - c1.y0;
	double denom = dxb * dya - dxa * dyb;
	double y;
	if (denom != 0) {
	    double num = ((x0 - c1.x0) * dya * dyb
			  - y0 * dxa * dyb
			  + c1.y0 * dxb * dya);
	    y = num / denom;
	    if (y <= yrange[0]) {
		// intersection is above us
		// Use bottom-most common y for comparison
		y = Math.min(y1, c1.y1);
	    } else {
		// intersection is below the top of our range
		if (y < yrange[1]) {
		    // If intersection is in our range, adjust valid range
		    yrange[1] = y;
		}
		// Use top-most common y for comparison
		y = Math.max(y0, c1.y0);
	    }
	} else {
	    // lines are parallel, choose any common y for comparison
	    // Note - prefer an endpoint for speed of calculating the X
	    // (see shortcuts in Order1.XforY())
	    y = Math.max(y0, c1.y0);
	}
	return orderof(XforY(y), c1.XforY(y));
    }

    public int getSegment(double coords[]) {
	if (direction == INCREASING) {
	    coords[0] = x1;
	    coords[1] = y1;
	} else {
	    coords[0] = x0;
	    coords[1] = y0;
	}
	return PathIterator.SEG_LINETO;
    }
}
